/**
 * FleetNode.java
 *
 * Roslan's Remake of MOO in 3D using Ardor3D
 * (C) 2011 Roslan Amir. All rights reserved.
 */

package com.roslan.games.moo3d.spatial;

import java.awt.Point;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;

import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Quaternion;
import com.ardor3d.math.Vector2;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.state.MaterialState;
import com.ardor3d.renderer.state.MaterialState.ColorMaterial;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.hint.DataMode;
import com.ardor3d.scenegraph.shape.Pyramid;
import com.roslan.games.moo3d.GameData;
import com.roslan.games.moo3d.data.Fleet;
import com.roslan.games.moo3d.data.Race;
import com.roslan.games.moo3d.data.ShipType;
import com.roslan.games.moo3d.data.Star;
import com.roslan.games.moo3d.data.Star.Environment;

/**
 * A <code>FleetNode</code> is a <code>Node</code> representing a fleet displayed on the galaxy map.
 * 
 * @author Roslan Amir
 * @version 1.0 - Jan 16, 2011
 */
public class FleetNode extends Node {

	public static final double VERTICAL_GAP = 0.125;

	public static final double HORIZONTAL_GAP = 0.6;

//	/**
//	 * Radius of the cylinder representing a fleet shape.
//	 */
//	private static final double FLEET_CYLINDER_RADIUS = 0.25;

//	/**
//	 * Height of the cylinder representing a fleet shape.
//	 */
//	private static final double FLEET_CYLINDER_HEIGHT = 0.125;

	/**
	 * Wifth of the pyramid representing a fleet shape.
	 */
	private static final double FLEET_PYRAMID_WIDTH = 0.5;

	/**
	 * Height of the cylinder representing a fleet shape.
	 */
	private static final double FLEET_PYRAMID_HEIGHT = 0.5;

	/**
	 * A list of discarded/unused <code>FleetNode</code> objects. A new <code>FleetNode</code> request is taken off this pool. If the list is empty, a new
	 * instance of <code>FleetNode</code> is created. When is <code>FleetNode</code> is no longer used, it is put back on this pool.
	 */
	private static final Deque<FleetNode> pool = new ArrayDeque<FleetNode>();

//	/**
//	 * The Mesh to represent a fleet visual.
//	 */
//	private static final Cylinder baseFleetMesh;

	/**
	 * The Mesh to represent a fleet visual.
	 */
	private static final Pyramid baseFleetMesh;

//	/**
//	 * This Node model represents the base node for a fleet visual.
//	 */
//	private static final Node baseFleetModelNode;

	/*
	 * Create the sample Mesh as the shape for the fleet.
	 */
	static {
//		/*
//		 * Create the Mesh to be used as the visual representation for a fleet.
//		 */

//		/*
//		 * Create the Mesh to be used as the visual representation for a fleet.
//		 */
//		baseFleetMesh = createFleetMeshAsCylinder();

		/*
		 * Create the Mesh to be used as the visual representation for a fleet.
		 */
		baseFleetMesh = createFleetMeshAsPyramid();

//		/*
//		 * Load the model to be used as the visual representation for a fleet.
//		 */
//		baseFleetModelNode = importFleetModel();
	}

//	/**
//	 * Create the Mesh to be used as the visual representation for a fleet.
//	 */
//	private static Cylinder createFleetMeshAsCylinder() {
//		Cylinder mesh = new Cylinder("FleetCylinder", 64, 64, FLEET_CYLINDER_RADIUS, FLEET_CYLINDER_HEIGHT);
//		mesh.updateModelBound();
//		mesh.setModelBound(new BoundingBox());
//		mesh.getSceneHints().setDataMode(DataMode.VBO);
//
//		final MaterialState materialState = new MaterialState();
//		materialState.setColorMaterial(ColorMaterial.Emissive);
//		mesh.setRenderState(materialState);
//
//		return mesh;
//	}

	/**
	 * Create the Mesh to be used as the visual representation for a fleet.
	 */
	private static Pyramid createFleetMeshAsPyramid() {
		Pyramid mesh = new Pyramid("FleetPyramid", FLEET_PYRAMID_WIDTH, FLEET_PYRAMID_HEIGHT);
		mesh.updateModelBound();
		mesh.setModelBound(new BoundingBox());
		mesh.getSceneHints().setDataMode(DataMode.VBO);

		final Quaternion rotation1 = Quaternion.fetchTempInstance();
		rotation1.fromAngleNormalAxis(90.0 * MathUtils.DEG_TO_RAD, Vector3.UNIT_X);
		mesh.setRotation(rotation1);
		Quaternion.releaseTempInstance(rotation1);

		final MaterialState materialState = new MaterialState();
		materialState.setColorMaterial(ColorMaterial.Emissive);
		mesh.setRenderState(materialState);

		return mesh;
	}

//	/**
//	 * Comment for method.
//	 *
//	 * @return 
//	 */
//	private static Node importFleetModel() {
//		final ObjImporter importer = new ObjImporter();
////		final ObjGeometryStore storage = importer.load("models/obj/SPINDRIFT.obj");
//		final ObjGeometryStore storage = importer.load("models/obj/shipA_OBJ.obj");
//
//		Node node = storage.getScenegraph();
//		node.setName("FleetModelNode");
////		node.setScale(0.08); // for SPINDRIFT
//		node.setScale(0.01); // for shipA_OBJ
//		node.updateWorldBound(true);
//
//		final Quaternion rotation1 = Quaternion.fetchTempInstance();
////		final Quaternion rotation2 = Quaternion.fetchTempInstance();
//		rotation1.fromAngleNormalAxis(90.0 * MathUtils.DEG_TO_RAD, Vector3.UNIT_X);
////		rotation2.fromAngleNormalAxis(180.0 * MathUtils.DEG_TO_RAD, Vector3.UNIT_Y);
////		rotation1.multiplyLocal(rotation2);
//		node.setRotation(rotation1);
//		Quaternion.releaseTempInstance(rotation1);
////		Quaternion.releaseTempInstance(rotation2);
//
//		return node;
//	}

	/**
	 * Fleet status.
	 */
	public enum FleetStatus {
		ORBITING, DEPARTING, ENROUTE
	}

	/**
	 * The indexes for the tile where this fleet is in the 2-dimensional array representing the galaxy map.
	 */
	public final Point tile = new Point();

	/**
	 * The current fleet status: ORBITING, DEPARTING, ENROUTE
	 */
	public FleetStatus status;

	/**
	 * The star around which this fleet is orbiting. Null means the fleet is currently in transit.
	 */
	public StarNode orbiting;

	/**
	 * The destination for this fleet. Null means the fleet is in orbit around the star specified by "orbiting".
	 */
	public StarNode destination;

	/**
	 * The distance (in parsecs) of this fleet to its destination.
	 */
	public int distanceToDestination;

	/**
	 * How many turns before this fleet arrives at its destination.
	 */
	public int turnsToArrive;

	/**
	 * Holds the list of hex tiles to the destination star (used while the fleet is in transit)
	 */
	public final Deque<Vector2> pathTilesList = new ArrayDeque<Vector2>();

	/** 
	 * Holds the list of vectors from the current tile to the next tile along the path (used while the fleet is in transit)
	 */
	public final Deque<Vector3> pathVectorsList = new ArrayDeque<Vector3>();

	/**
	 * Constructor.
	 * 
	 * @param name - name of this <code>FleetNode</code>
	 */
	private FleetNode(String name) {
		super(name);
	}

	/**
	 * Return the <code>Fleet</code> instance embedded within this <code>FleetNode</code>.
	 * 
	 * @return the <code>Fleet</code> instance embedded within this <code>FleetNode</code>
	 */
	public Fleet getFleet() {
		return (Fleet) this.getUserData();
	}

	/**
	 * Merge the given source fleet into this fleet.
	 * 
	 * @param sourceFleetNode - the source fleet to merge
	 */
	public void merge(FleetNode sourceFleetNode) {
		this.getFleet().add(sourceFleetNode.getFleet());
	}

	/**
	 * Check if this fleet can colonize this star.
	 * 
	 * @param star - the star
	 * @return <code>true</code> if this fleet can colonize this star, <code>false</code> otherwise
	 */
	public boolean canColonize(Star star) {
		if (star.environment == Environment.NONE)
			return false;
		int starEnv = star.environment.ordinal();
		int minShipEnv = Environment.NONE.ordinal();
		Fleet fleet = this.getFleet();
		for (int i = 0; i < ShipType.TYPE_COUNT; i++) {
			if (fleet.shipCount[i] > 0) {
				if (minShipEnv > fleet.owner.shipTypes[i].colonizer.ordinal()) {
					minShipEnv = fleet.owner.shipTypes[i].colonizer.ordinal();
				}
			}
		}
		return minShipEnv <= starEnv;
	}

	/**
	 * Create a new fleet for the given race.
	 * 
	 * @param race - the race who owns this fleet
	 * @return the newly created <code>FleetNode</code>
	 */
	public static FleetNode createFleetNode(Race race) {
		FleetNode fleetNode;
		Fleet fleet;
		if (pool.isEmpty()) {
			fleetNode = new FleetNode("FleetNode");

			/*
			 * Used only if a ship model is loaded as the Mesh for a fleet visual.
			 */
//			final Node spatial = baseFleetModelNode.makeCopy(true);
//			spatial.updateWorldBound(true);
//			fleetNode.attachChild(spatial);

			/*
			 * Used only if a cylinder is used as the Mesh for a fleet visual.
			 */
			final Mesh spatial = baseFleetMesh.makeCopy(true);
			spatial.updateModelBound();
			final MaterialState renderState = new MaterialState();
			renderState.setAmbient(GameData.BANNER_COLORS[race.bannerIndex]);
			spatial.setRenderState(renderState);
			fleetNode.attachChild(spatial);

			/*
			 * Add a banner to the fleet.
			 */
			final FleetBannerBillboard fleetBannerBillboard = new FleetBannerBillboard(race.bannerIndex);
//			fleetBannerBillboard.setTranslation(0, 0, 0.4);
			fleetBannerBillboard.setTranslation(0.0, 0.0, 0.55);
			fleetNode.attachChild(fleetBannerBillboard);

			/*
			 * Attach the Fleet data object to this node.
			 */
			fleet = new Fleet();
			fleetNode.setUserData(fleet);
		} else {
			fleetNode = pool.pop();
			fleet = fleetNode.getFleet();
			fleet.maxRange = 0;
			fleet.warpSpeed = 0;
			Arrays.fill(fleet.shipCount, 0);
		}
		FleetBannerBillboard fleetBannerBillboard = (FleetBannerBillboard) fleetNode.getChild(1);
		fleetBannerBillboard.setBanner(race.bannerIndex);
		fleet.owner = race;

		/*
		 * Add the the new fleet to the race list.
		 */
		GameData.addFleetToRaceList(race, fleetNode);

		return fleetNode;
	}

	/**
	 * Create the initial fleet for the specified race.
	 * 
	 * @param race - the race whose fleet we are creating
	 * @return the <code>FleetNode</code> just created
	 */
	public static FleetNode createInitialFleet(Race race) {
		FleetNode fleetNode = FleetNode.createFleetNode(race);
		Fleet fleet = fleetNode.getFleet();
		fleet.addShips(0, 2); // 2 scouts
		fleet.addShips(4, 1); // 1 colony ship
		return fleetNode;
	}

	/**
	 * Dispose the unused <code>FleetNode</code> by putting it back into the unused pool.
	 *
	 * @param fleetNode - the <code>FleetNode</code> to dispose.
	 */
	public static void dispose(FleetNode fleetNode) {
		pool.push(fleetNode);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "FleetNode [tile=(" + tile.x + ',' + tile.y + "), status=" + status + ", orbiting=" + orbiting + ", destination=" + destination + ", distanceToDestination=" + distanceToDestination + ", turnsToArrive=" + turnsToArrive + "]";
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return this.getName().hashCode();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		FleetNode other = (FleetNode) obj;
		if (destination == null) {
			if (other.destination != null)
				return false;
		} else if (!destination.equals(other.destination))
			return false;
		if (distanceToDestination != other.distanceToDestination)
			return false;
		if (orbiting == null) {
			if (other.orbiting != null)
				return false;
		} else if (!orbiting.equals(other.orbiting))
			return false;
		if (status != other.status)
			return false;
		if (turnsToArrive != other.turnsToArrive)
			return false;
		if (tile.x != other.tile.x)
			return false;
		if (tile.y != other.tile.y)
			return false;
		return true;
	}

}
